package com.partsoft.dits.integrate;



import java.io.IOException;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.Properties;

import javax.sql.DataSource;

import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;

import com.partsoft.dits.ConfigurableBean;
import com.partsoft.dits.DataDescriptorManager;
import com.partsoft.dits.dsm.DataSourceFactory;
import com.partsoft.dits.dsm.DefaultDsmSQLProviderFactory;
import com.partsoft.dits.dsm.DsmOriginalSQLs;
import com.partsoft.dits.dsm.DsmSQLProvider;
import com.partsoft.dits.dsm.DsmSQLProviderFactory;
import com.partsoft.dits.log.Log;
import com.partsoft.dits.utils.DbUtils;
import com.partsoft.dits.utils.ExecutionCallback;
import com.partsoft.dits.utils.JdbcTemplate;

import org.springframework.transaction.interceptor.TransactionAttribute;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

/**
 * 数据表发布描述符管理器
 */
public class ContextTableBroadcastDescriptorManager extends
		ConfigurableTableDataDescriptorManager<TableBroadcastEdgeDescriptor> implements
		TableBroadcastDescriptorManager, TableDataDescriptorRegistry<TableBroadcastEdgeDescriptor>,
		DataDescriptorManager<TableBroadcastEdgeDescriptor>, ConfigurableBean, ApplicationContextAware,
		ApplicationListener<ApplicationEvent> {

	private DsmSQLProviderFactory dsmSQLProviderFactory = DefaultDsmSQLProviderFactory.get();

	private String monitorLogTable = DsmOriginalSQLs.Constants.MonitorLogTable;

	private String monitorLogNS = DsmOriginalSQLs.Constants.MonitorLogNS;

	private DataSourceFactory dataSourceFactory;

	public ContextTableBroadcastDescriptorManager() {
	}

	public ContextTableBroadcastDescriptorManager(String contextConfigLocation,
			String transformScriptDirectoryLocation, String descriptorIdPrefix) {
		super(contextConfigLocation, transformScriptDirectoryLocation, descriptorIdPrefix);
	}

	public ContextTableBroadcastDescriptorManager(String contextConfigLocation,
			String transformScriptDirectoryLocation, String descriptorIdPrefix, String triggerNamePrefix,
			boolean createNoExists) {
		super(contextConfigLocation, transformScriptDirectoryLocation, descriptorIdPrefix, createNoExists);
	}

	public void setDsmSQLProviderFactory(DsmSQLProviderFactory dsmSQLProviderFactory) {
		this.dsmSQLProviderFactory = dsmSQLProviderFactory;
	}

	public void setMonitorLogNS(String monitorLogNS) {
		this.monitorLogNS = monitorLogNS;
	}

	public void setMonitorLogTable(String monitorLogTable) {
		this.monitorLogTable = monitorLogTable;
	}

	public void setDataSourceFactory(DataSourceFactory dataSourceFactory) {
		this.dataSourceFactory = dataSourceFactory;
	}
	
	public DataSourceFactory getDataSourceFactory() {
		return dataSourceFactory;
	}

	private String resolveDescriptorTriggerPrefix(String schemaName, String tableName) {
		Assert.hasText(schemaName);
		Assert.hasText(tableName);
		schemaName = schemaName.trim().toUpperCase();
		tableName = tableName.trim().toUpperCase();
		StringBuffer buffer = new StringBuffer();
		buffer.append(schemaName).append(".").append(tableName);
		return buffer.toString();
	}

	private String resolveDescriptorTriggerPrefix(TableBroadcastEdgeDescriptor descriptor) {
		return resolveDescriptorTriggerPrefix(descriptor.getSchemaName(), descriptor.getTableName());
	}

	private String resolveDescriptorUpdateTriggerName(TableBroadcastEdgeDescriptor descriptor) {
		return StringUtils.hasText(descriptor.getUpdateTriggerName()) ? descriptor.getUpdateTriggerName()
				: resolveDescriptorTriggerPrefix(descriptor) + "_U";
	}

	private String resolveDescriptorInsertTriggerName(TableBroadcastEdgeDescriptor descriptor) {
		return StringUtils.hasText(descriptor.getInsertTriggerName()) ? descriptor.getInsertTriggerName()
				: resolveDescriptorTriggerPrefix(descriptor) + "_I";
	}

	private String resolveDescriptorDeleteTriggerName(TableBroadcastEdgeDescriptor descriptor) {
		return StringUtils.hasText(descriptor.getDeleteTriggerName()) ? descriptor.getDeleteTriggerName()
				: resolveDescriptorTriggerPrefix(descriptor) + "_D";
	}

	/**
	 * 撤销描述符触发器
	 */
	private void unDeployMonitorTrigger(TableBroadcastEdgeDescriptor tbcDesc) {
		Assert.notNull(tbcDesc);
		String dsName = tbcDesc.getDataSourceName();
		final DataSource ds = dataSourceFactory.getDataSource(dsName);
		final List<String> unDeploySQLs = new LinkedList<String>();
		String wellBeUnMonitorTable = DbUtils.packSchemaTableName(tbcDesc.getSchemaName(), tbcDesc.getTableName());
		DsmSQLProvider dsmSQLProvider = dsmSQLProviderFactory.getDsmSQLProvider(ds);

		if (tbcDesc.isInsertMonitored()) {
			String[] dropSQLs = dsmSQLProvider.getDropInsertMonitorSQLs(wellBeUnMonitorTable,
					tbcDesc.getKeyFieldNames(), tbcDesc.getInsertTriggerName(), monitorLogTable, monitorLogNS);
			unDeploySQLs.addAll(Arrays.asList(dropSQLs));
		}
		if (tbcDesc.isUpdateMonitored()) {
			String[] dropSQLs = dsmSQLProvider.getDropUpdateMonitorSQLs(wellBeUnMonitorTable,
					tbcDesc.getKeyFieldNames(), tbcDesc.getUpdateTriggerName(), monitorLogTable, monitorLogNS);
			unDeploySQLs.addAll(Arrays.asList(dropSQLs));
		}
		if (tbcDesc.isDeleteMonitored()) {
			String[] dropSQLs = dsmSQLProvider.getDropDeleteMonitorSQLs(wellBeUnMonitorTable,
					tbcDesc.getKeyFieldNames(), tbcDesc.getDeleteTriggerName(), monitorLogTable, monitorLogNS);
			unDeploySQLs.addAll(Arrays.asList(dropSQLs));
		}
		if (unDeploySQLs.size() == 0)
			return;

		DbUtils.executeInTransaction(ds, TransactionAttribute.PROPAGATION_REQUIRES_NEW, new ExecutionCallback() {
			public void callback() {
				JdbcTemplate jdbcHelp = new JdbcTemplate(ds);
				for (String unDeploySQL : unDeploySQLs) {
					jdbcHelp.execute(unDeploySQL);
				}
			}
		});
	}

	/**
	 * 部署描述符触发器
	 */
	private void deployMonitorTrigger(TableBroadcastEdgeDescriptor tbcDesc) {
		Assert.notNull(tbcDesc);
		final DataSource ds = dataSourceFactory.getDataSource(tbcDesc.getDataSourceName());
		final List<String> triggerSQLs = new LinkedList<String>();

		String wellBeMonitorTable = DbUtils.packSchemaTableName(tbcDesc.getSchemaName(), tbcDesc.getTableName());
		DsmSQLProvider dsmSQLProvider = dsmSQLProviderFactory.getDsmSQLProvider(ds);

		if (tbcDesc.isInsertMonitored()) {
			String[] createSQLs = dsmSQLProvider.getCreateInsertMonitorSQLs(wellBeMonitorTable,
					tbcDesc.getKeyFieldNames(), tbcDesc.getInsertTriggerName(), monitorLogTable, monitorLogNS);
			triggerSQLs.addAll(Arrays.asList(createSQLs));
		}
		if (tbcDesc.isUpdateMonitored()) {
			String[] createSQLs = dsmSQLProvider.getCreateUpdateMonitorSQLs(wellBeMonitorTable,
					tbcDesc.getKeyFieldNames(), tbcDesc.getUpdateTriggerName(), monitorLogTable, monitorLogNS);
			triggerSQLs.addAll(Arrays.asList(createSQLs));
		}
		if (tbcDesc.isDeleteMonitored()) {
			String[] createSQLs = dsmSQLProvider.getCreateDeleteMonitorSQLs(wellBeMonitorTable,
					tbcDesc.getKeyFieldNames(), tbcDesc.getDeleteTriggerName(), monitorLogTable, monitorLogNS);
			triggerSQLs.addAll(Arrays.asList(createSQLs));
		}
		if (triggerSQLs.size() == 0)
			return;
		DbUtils.executeInTransaction(ds, TransactionAttribute.PROPAGATION_REQUIRES_NEW, new ExecutionCallback() {
			public void callback() {
				JdbcTemplate jdbcHelper = new JdbcTemplate(ds);
				for (String triggerSQL : triggerSQLs) {
					jdbcHelper.execute(triggerSQL);
				}
			}
		});
	}

	@Override
	protected void beforeRemoveDescriptor(TableBroadcastEdgeDescriptor descriptor) {
		synchronized (this) {
			if (1 >= countDescriptor(descriptor.getDataSourceName(), descriptor.getSchemaName(),
					descriptor.getTableName())) {
				unDeployMonitorTrigger(descriptor);
			}
		}
	}

	@Override
	protected void beforeLoadConfiguredDescriptor(TableBroadcastEdgeDescriptor descriptor) {
		try {
			descriptor.setTransformScript(loadTransformScriptContent(descriptor));
		} catch (IOException e) {
			String errMsg = String.format("加载(%s)的转换脚本失败: %s", descriptor.toString(), e.getMessage());
			Log.error(errMsg, e);
			throw new IllegalStateException(errMsg, e);
		}
	}

	@Override
	protected void setupDescriptorIdentifier(TableBroadcastEdgeDescriptor descriptor, String id) {
		descriptor.setId(id);
	}

	@Override
	protected Properties beforeAddDescriptor(TableBroadcastEdgeDescriptor descriptor) {
		synchronized (this) {

			// 保存脚本
			descriptor.setTransformScriptPath(resolveDescriptorScriptRelativeFileName(descriptor));
			try {
				saveTransformScriptContext(descriptor);
			} catch (IOException e) {
				throw new IllegalStateException(e.getMessage(), e);
			}

			descriptor.setInsertTriggerName(resolveDescriptorInsertTriggerName(descriptor));
			descriptor.setUpdateTriggerName(resolveDescriptorUpdateTriggerName(descriptor));
			descriptor.setDeleteTriggerName(resolveDescriptorDeleteTriggerName(descriptor));

			// 部署脚本
			if (0 == countDescriptor(descriptor.getDataSourceName(), descriptor.getSchemaName(),
					descriptor.getTableName())) {
				deployMonitorTrigger(descriptor);
			}

			Properties beanProps = new Properties();
			beanProps.setProperty("dataSourceName", descriptor.getDataSourceName());
			beanProps.setProperty("schemaName", descriptor.getSchemaName());
			beanProps.setProperty("tableName", descriptor.getTableName());
			beanProps.setProperty("keyFields", descriptor.getKeyFields());
			beanProps.setProperty("messageCode", descriptor.getMessageCode());
			beanProps.setProperty("transformScriptPath", descriptor.getTransformScriptPath());
			beanProps.setProperty("insertTriggerName", descriptor.getInsertTriggerName());
			beanProps.setProperty("updateTriggerName", descriptor.getUpdateTriggerName());
			beanProps.setProperty("deleteTriggerName", descriptor.getDeleteTriggerName());
			beanProps.setProperty("conversion", new Boolean(descriptor.isConversion()).toString());
			beanProps.setProperty("insertMonitored", new Boolean(descriptor.isInsertMonitored()).toString());
			beanProps.setProperty("updateMonitored", new Boolean(descriptor.isUpdateMonitored()).toString());
			beanProps.setProperty("deleteMonitored", new Boolean(descriptor.isDeleteMonitored()).toString());
			beanProps.setProperty("publishStandard", new Boolean(descriptor.isPublishStandard()).toString());
			beanProps.setProperty("title", descriptor.getTitle());
			beanProps.setProperty("description", descriptor.getDescription());
			beanProps.setProperty("ordinal", Integer.valueOf(descriptor.getOrdinal()).toString());

			return beanProps;
		}
	}

	@Override
	protected String resolveDescriptorScriptRelativeFileName(TableBroadcastEdgeDescriptor descriptor) {
		return StringUtils.hasText(descriptor.getTransformScriptPath()) ? descriptor.getTransformScriptPath() : super
				.resolveDescriptorScriptRelativeFileName(descriptor);
	}

	@Override
	protected void validateDescriptor(TableBroadcastEdgeDescriptor descriptor) {
		super.validateDescriptor(descriptor);
		Assert.isTrue(descriptor.getKeyColumns().length == 1, "must set one key column for monitor table change");
		if (countDescriptor(descriptor.getDataSourceName(), descriptor.getSchemaName(), descriptor.getTableName()) > 0) {
			TableBroadcastEdgeDescriptor[] tbds = getDescriptors(descriptor.getDataSourceName(),
					descriptor.getSchemaName(), descriptor.getTableName());
			Assert.isTrue(ObjectUtils.nullSafeEquals(tbds[0].getKeyFieldNames(), descriptor.getKeyFieldNames()), String
					.format("have old monitor (%s.%s.%s)， but old key is (%s) not equals (%s)",
							descriptor.getDataSourceName(), descriptor.getSchemaName(), descriptor.getTableName(),
							tbds[0].getKeyFieldNames(), descriptor.getKeyFieldNames()));
		}
	}

	public Class<TableBroadcastEdgeDescriptor> getSupportClass() {
		return TableBroadcastEdgeDescriptor.class;
	}

	@Override
	public void initContext() {
		Assert.notNull(dataSourceFactory, "必须设置数据源工厂参数。");
		super.initContext();
	}

}
